package com.googlecode.tidej.client.editor;

import java.util.TreeSet;

import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.google.gwt.event.dom.client.TouchEndEvent;
import com.google.gwt.event.dom.client.TouchEndHandler;
import com.google.gwt.event.dom.client.TouchStartEvent;
import com.google.gwt.event.dom.client.TouchStartHandler;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.googlecode.tidej.client.Console;
import com.googlecode.tidej.client.codemirror.CodeMirror;
import com.googlecode.tidej.client.codemirror.CodeMirror.LocalVar;
import com.googlecode.tidej.client.codemirror.CodeMirror.Pos;
import com.googlecode.tidej.client.codemirror.CodeMirror.Token;
import com.googlecode.tidej.shared.model.Model;
import com.googlecode.tidej.shared.model.Operation;
import com.googlecode.tidej.shared.model.Parameter;
import com.googlecode.tidej.shared.model.Property;
import com.googlecode.tidej.shared.model.Type;

public class SuggestList extends Composite {
  Model model;
  static TreeSet<String> statements = new TreeSet<String>();
  FlowPanel listBox = new FlowPanel();
  ScrollPanel scrollPanel = new ScrollPanel(listBox);
  private CodeMirror codeMirror;
  private CodeEditor editor;
  static int count = 0;
  final int id = ++count;
  TouchStartHandler touchStartHandler = new TouchStartHandler() {
    @Override
    public void onTouchStart(TouchStartEvent event) {
      event.preventDefault();
    }
  };
  TouchEndHandler touchEndHandler = new TouchEndHandler() {
    @Override
    public void onTouchEnd(TouchEndEvent event) {
      Element element = Element.as(event.getNativeEvent().getEventTarget());
      insertText(element.getInnerText());
      event.preventDefault();
    }
  };
  MouseDownHandler mouseDownHandler = new MouseDownHandler() {
    @Override
    public void onMouseDown(MouseDownEvent event) {
      event.preventDefault();
    }
  };
  MouseUpHandler mouseUpHandler = new MouseUpHandler() {
    @Override
    public void onMouseUp(MouseUpEvent event) {
      Element element = Element.as(event.getNativeEvent().getEventTarget());
      insertText(element.getInnerText());
      event.preventDefault();
    }
  };
  
  int currentLine;
  int currentCh;
  
  static {
    statements.add("do {");
    statements.add("while (");
    statements.add("for (var ");
    statements.add("if (");
    statements.add("var ");
    statements.add("try {");
    statements.add("delete");
    statements.add("switch (");
  }
  
  public SuggestList(Model model) {
    this.model = model;
    listBox.setStyleName("SuggestList");
    initWidget(listBox);
  }
  
  public void setEditor(CodeEditor editor) {
    this.editor = editor;
    this.codeMirror = editor.codeMirror;
  }
  
  public static native void setGlobal(String name, JavaScriptObject jso) /*-{
    $wnd[name] = jso;
  }-*/;
  

  private Token getTokenAt(CodeMirror.Pos pos) {
    return codeMirror.getTokenAt(pos);
  }

  
  private Token getPrevious(Token token) {
    if (token == null) {
      return null;
    }
    Token prev = getTokenAt(Pos.create(currentLine, token.getStart()));
//    Console.log("get previous of " + dbg(token) + " is: " + dbg(prev));
    return prev;
  }
  
  private Type getType(LocalVar localVars, String name) {
  //  Console.log("Searching var: " + name);
    while (localVars != null) {
//      Console.log("checking var " + localVars.getName());
      if (name.equals(localVars.getName())) {
        return localVars.getType();
      }
      localVars = localVars.getNext();
    }
    Console.log("Variable not found");
    return null;
  }
  
  private Type getType(Token token) {
    if (token == null) {
      return null;
    }
    Console.log("determining type for " + token.toDebugString());
    if (token.getClassName().startsWith("variable")) {
      return getType(token.getState().getLocalVars(), token.getString());
    }
    return null;
  }
  
  public void update() {
    Pos newPos = codeMirror.getCursor();
    if (currentLine == newPos.getLine() && currentCh == newPos.getCh()) {
      return;
    }
    currentLine = newPos.getLine();
    currentCh = newPos.getCh();
    
    listBox.clear();
//    listBox.add(new Label("debug value: token"+id));
    Token token = getTokenAt(codeMirror.getCursor());
    Token previousToken = getPrevious(token);
    Token previousToken2 = getPrevious(previousToken);
    Token previousNonEmpty = previousToken.getString().trim().length() == 0 ? previousToken2 : previousToken;
    Token nonEmptyToken = token.getString().trim().length() == 0 ? previousNonEmpty : token;

    setGlobal("token", token);
    Console.log("token: " + token.toDebugString() + " prev: " + previousToken.toDebugString()
        + " prev2: " + previousToken2.toDebugString());
    
    TreeSet<String> newSuggestList = new TreeSet<String>();

    if (".".equals(token.getString()) || "property".equals(token.getClassName())) {
      processProperty(token, newSuggestList);
    } else {
      String start = token.getString().trim();
      
      if (start.length() == 1 && "()+-*/|&=:;?[]".indexOf(start.charAt(0)) != -1) {
        start = "";
      }
      
      // Statement start
      if ("".equals(previousNonEmpty.getString().trim()) || 
          ";".equals(nonEmptyToken.getString().trim())) {
        for(String s: statements.tailSet(start)) {
          if (!s.startsWith(start)) {
            break;
          }
          newSuggestList.add(s);
        }
      }
      // All variables
      LocalVar lv = token.getState().getLocalVars();
      while(lv != null) {
        Object o = lv.getObject();
        if (start.length() > 0 || o instanceof Parameter || lv.getName().equals("this")) {
          String name = lv.getName();
          if (lv.getObject() instanceof Operation) {
            name += "(";
          }
          if (name.startsWith(start)) {
            newSuggestList.add(name);
          }
        }
        lv = lv.getNext();
      }
      if ("new ".startsWith(start)) {
        newSuggestList.add("new ");
      }
    }

    for (String s: newSuggestList) {
      Label label = new Label(s);
      label.addMouseDownHandler(mouseDownHandler);
      label.addMouseUpHandler(mouseUpHandler);
      label.addTouchStartHandler(touchStartHandler);
      label.addTouchEndHandler(touchEndHandler);
      listBox.add(label);
    }
  }
  

  private void processProperty(Token token, TreeSet<String> newSuggestList) {
    Type type;
    String start;
    if (".".equals(token.getString())) {
      type = getType(getPrevious(token));
      start = "";
    } else {
      start = token.getString();
      type = getType(getPrevious(getPrevious(token)));
    }
    Console.log("Type determined: " + type);
    if (type != null) {
      for (Property p : type.getOwnProperties()) {
        if (p.getName().startsWith(start)) {
          newSuggestList.add(p.getName());
        }
      }
      for (Operation o: type.getOwnOperations()) {
        if (o.getName().startsWith(start)) {
          newSuggestList.add(o.getName() + "(");
        }
      }
    }
  }

  private void insertText(String s) {
    Pos pos = codeMirror.getCursor();
    String prefix = getTokenAt(pos).getString();
    if (s.startsWith(prefix)) {
      s = s.substring(prefix.length());
    } else {
      s = " " + s;
    }
    codeMirror.replaceRange(s, pos);
    
    // Delay needed for iPad
//    new Timer() {
//      @Override
//      public void run() {
//        editor.focus();
//      }
//    }.schedule(1000);
  }
}
